package com.lifang.framework.core;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 方法参数名称解析
 * 虚拟机编译后，默认方法的名称都会变成 var1 var2 等等
 * 原方法参数名称就只能通过字节码寻找
 */
public class LocalVariableParameterNameDiscoverer implements ParameterNameDiscoverer {

    private final Logger logger = LoggerFactory.getLogger(LocalVariableParameterNameDiscoverer.class);

    private final String[] emptyStringArray = new String[]{};

    @Override
    public String[] getParameterNames(Method method) {
        Assert.notNull(method, "method must not be null");
        Class<?> clazz = method.getDeclaringClass();
        try (InputStream in = clazz.getResourceAsStream("/" + clazz.getName().replace('.', '/') + ".class")) {
            return getMethodParamNames(in, method).toArray(new String[0]);
        } catch (Throwable th) {
            logger.trace("参数名称寻找异常：{}",th.getMessage());
            return emptyStringArray;
        }
    }

    @Override
    public String[] getParameterNames(Constructor<?> ctor) {
        Assert.notNull(ctor, "constructor must not be null");
        Class<?> clazz = ctor.getDeclaringClass();
        try (InputStream inputStream = clazz.getResourceAsStream("/" + clazz.getName().replace('.', '/') + ".class")) {
            int paramSize = ctor.getParameterTypes().length;
            String descriptor = Type.getConstructorDescriptor(ctor);
            EnclosingMetadata metadata = new EnclosingMetadata(ctor.getName(), descriptor, paramSize);
            return getParamNames(inputStream, metadata).toArray(new String[0]);
        } catch (Throwable th) {
            logger.trace("参数名称寻找异常：{}",th.getMessage());
            return emptyStringArray;
        }
    }

    private List<String> getMethodParamNames(InputStream in, Method method) throws IOException {
        try (InputStream inputStream = in) {
            return getParamNames(inputStream, new EnclosingMetadata(method.getName(), Type.getMethodDescriptor(method), method.getParameterTypes().length));
        }
    }

    @SuppressWarnings("unchecked")
    private List<String> getParamNames(InputStream inputStream, EnclosingMetadata metadata) throws IOException {
        ClassReader classReader = new ClassReader(inputStream);
        ClassNode classNode = new ClassNode();
        classReader.accept(classNode, ClassReader.EXPAND_FRAMES);
        List<String> paramNamesList = new ArrayList<>();
        MethodNode methodNode = getMethodNode(classNode, metadata);
        if (methodNode != null) {
            List<LocalVariableNode> localVariables = methodNode.localVariables;
            List<LocalVariable> varNames = new ArrayList<>();
            for (LocalVariableNode localVariableNode : localVariables) {
                int index = localVariableNode.index;
                String varName = localVariableNode.name;
                if (!"this".equals(varName)) {
                    varNames.add(new LocalVariable(index, varName));
                }
            }
            if (CollectionUtil.isNotEmpty(varNames)) {
                varNames = varNames.stream().sorted().collect(Collectors.toList());
                for (int i = 0; i < metadata.size; i++) {
                    paramNamesList.add(varNames.get(i).name);
                }
            }
        }
        return paramNamesList;
    }

    @SuppressWarnings("unchecked")
    private MethodNode getMethodNode(ClassNode classNode, EnclosingMetadata methodMeta) {
        String methodDescriptor = methodMeta.desc;
        String methodName = methodMeta.name;
        List<MethodNode> methods = classNode.methods;
        for (MethodNode methodNode : methods) {
            if (methodNode.desc.equals(methodDescriptor) || methodNode.name.equals(methodName)) {
                return methodNode;
            }
        }
        return null;
    }

    /**
     * 封装方法描述和参数个数
     */
    static class EnclosingMetadata {

        public String name;
        public String desc;
        public int size;

        public EnclosingMetadata(String name, String desc, int size) {
            this.name = name;
            this.desc = desc;
            this.size = size;
        }
    }

    static class LocalVariable implements Comparable<LocalVariable> {

        public int index;
        public String name;

        public LocalVariable(int index, String name) {
            this.index = index;
            this.name = name;
        }

        @Override
        public int compareTo(LocalVariable localVariable) {
            return this.index - localVariable.index;
        }
    }
}
